﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using log4net;
using ResxCrunch.Utils;
using System.IO;

namespace ResxCrunch.Business.Managers
{
    /// <summary>
    /// Data manager class
    /// </summary>   
    public class DataMngr : Singleton<DataMngr>
    {
        #region Constants
        /// <summary>
        /// Logger declaration
        /// </summary>
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        #endregion Constants

        #region Public Methods
        /// <summary>
        /// Selects the maximum value from the specified column of the datatable
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="columnName">Name of the integer type column.</param>
        /// <returns>
        /// Maximum selected value.
        /// If the maximum value is maller than 0, 0 is returned
        /// </returns>
        public int MaxValueFromColumn(DataTable table, string columnName)
        {
            int max = 0;

            if (table == null)
            {
                log.Error("Error finding max value, datatable is null");
                return max;
            }

            try
            {
                foreach (DataRow dr in table.Rows)
                {
                    if ((int)dr[columnName] > max)
                    {
                        max = (int)dr[columnName];
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error("Error finding max value", ex);
            }

            return max;
        }

        /// <summary>
        /// Applies filters to a datatable
        /// If the filters list is empty, the filters are removed
        /// </summary>
        /// <param name="originalTable">Table containing the datat that needs to be filtered</param>
        /// <param name="filteredTable">Table that containd the filtered data. 
        /// If not empty originally, then the contained data is first moved back to the original table then filtered again</param>
        /// <param name="filterPath">Path to a file that contains the list of filter values the data is filtered after.
        /// If there are no values, the only thing that is done is that the already filtered values get restored.
        /// The file structure: needs to be a serialized datatable that contains one column with the column name Filter</param>
        /// <param name="filterAfterColumnName">Name of the column that needs to be filtered based on its values</param>
        /// <returns>True on success</returns>
        public bool ApplyFiltersToDataTable(ref DataTable originalTable, ref DataTable filteredTable, string filterPath, string filterAfterColumnName)
        {
            return ApplyFiltersToDataTable(ref originalTable, ref filteredTable, GetFilters(filterPath, true), filterAfterColumnName);
        }

        /// <summary>
        /// Applies filters to a datatable
        /// If the filters list is empty, the filters are removed
        /// </summary>
        /// <param name="originalTable">Table containing the datat that needs to be filtered</param>
        /// <param name="filteredTable">Table that containd the filtered data. 
        /// If not empty originally, then the contained data is first moved back to the original table then filtered again</param>
        /// <param name="filters">List of filter values the data is filtered after.
        /// If there are no values, the only thing that is done is that the already filtered values get restored</param>
        /// <param name="filterAfterColumnName">Name of the column that needs to be filtered based on its values</param>
        /// <returns>True on success</returns>
        public bool ApplyFiltersToDataTable(ref DataTable originalTable, ref DataTable filteredTable, List<string> filters, string filterAfterColumnName)
        {
            if (originalTable == null)
            {
                log.Warn("Original data table is null, cannot apply filters to datatable");
                return false;
            }

            #region Move filteredtable data to original table
            if (filteredTable != null)
            {
                #region Make the 2 tables have the same structure
                // The originalTable is considered the base
                for (int i = filteredTable.Columns.Count - 1; i >= 0; i--)
                {
                    if (!originalTable.Columns.Contains(filteredTable.Columns[i].ColumnName))
                    {
                        filteredTable.Columns.RemoveAt(i);
                    }
                }

                foreach (DataColumn column in originalTable.Columns)
                {
                    if (!filteredTable.Columns.Contains(column.ColumnName))
                    {
                        filteredTable.Columns.Add(column.ColumnName, column.DataType);
                    }
                }
                #endregion Make the 2 tables have the same structure

                #region Move all rows from filtered to original
                for (int i = filteredTable.Rows.Count - 1; i >= 0; i--)
                {
                    originalTable.ImportRow(filteredTable.Rows[i]);
                    filteredTable.Rows[i].Delete();
                }
                #endregion Move all rows from filtered to original
            }
            #endregion Move filteredtable data to original table

            // Move all filtered to filterTable
            return FilterData(ref originalTable, out filteredTable, filters, filterAfterColumnName);
        }

        /// <summary>
        /// Loads the list of filters from the filters xml file
        /// The xml file is a serialized datatable
        /// </summary>
        /// <param name="filtersPath">Path to the filters xml file.</param>
        /// <param name="createFileIfMissing">If set to <c>true</c> the filters file is created.</param>
        /// <returns>List of filters from the file. 
        /// Null on error</returns>
        public List<string> GetFilters(string filtersPath, bool createFileIfMissing)
        {
            if (string.IsNullOrEmpty(filtersPath))
            {
                return null;
            }

            try
            {
                if (!File.Exists(filtersPath) && createFileIfMissing)
                {
                    this.CreateEmptyResxCrunchFiltersFile(filtersPath);
                }

                if (File.Exists(filtersPath))
                {
                    DataTable filtersTable = new DataTable();
                    filtersTable.ReadXml(filtersPath);

                    List<string> filters = new List<string>();

                    foreach (DataRow dr in filtersTable.Rows)
                    {
                        filters.Add(dr[Enums.FilterTableDataColumn.Filter].ToString());
                    }

                    return filters;
                }
            }
            catch (Exception ex)
            {
                log.Error("Error loading filters", ex);
            }

            return null;
        }

        /// <summary>
        /// Loads the list of filters from the filters xml file
        /// The xml file is a serialized datatable
        /// </summary>
        /// <param name="filtersPath">Path to the filters xml file.</param>
        /// <param name="createFileIfMissing">If set to <c>true</c> the filters file is created.</param>
        /// <returns>List of filters from the file. 
        /// Null on error</returns>
        public bool SaveFilters(string filtersPath, List<string> filters)
        {
            if (string.IsNullOrEmpty(filtersPath))
            {
                log.Error("Filters path was not provided");
                return false;
            }

            try
            {
                DataTable filtersTable = new DataTable(Utils.Constants.FilterTableName);
                filtersTable.Columns.Add(Utils.Enums.FilterTableDataColumn.Filter);

                foreach (string filter in filters)
                {
                    DataRow dr = filtersTable.NewRow();
                    dr[Utils.Enums.FilterTableDataColumn.Filter] = filter;
                    filtersTable.Rows.Add(dr);
                }

                filtersTable.WriteXml(filtersPath, XmlWriteMode.WriteSchema);

                return true;
            }
            catch (Exception ex)
            {
                log.Error("Error saving filters", ex);
            }

            return false;
        }

        /// <summary>
        /// Creates the empty filters file.
        /// - Creates a datatable with the name <see cref="Utils.Constants.FilterTableName">Utils.Constants.FilterTableName</see>
        /// - Adds a column with the name <see cref="Utils.Enums.FilterTableDataColumn.Filter">Utils.Enums.FilterTableDataColumn.Filter</see>
        /// - Saves the datatable as an xml
        /// </summary>
        /// <param name="path">The path where the file needs to be saved.</param>
        public void CreateEmptyResxCrunchFiltersFile(string path)
        {
            try
            {
                DataTable filtersTableTemp = new DataTable(Utils.Constants.FilterTableName);
                filtersTableTemp.Columns.Add(Utils.Enums.FilterTableDataColumn.Filter);

                filtersTableTemp.WriteXml(path, XmlWriteMode.WriteSchema);
            }
            catch (Exception ex)
            {
                log.Error("Could not create filter file", ex);
            }
        }

        /// <summary>
        /// Extracts the cultures from ResxCrunch data source.
        /// Culture means all the columns that are not <see cref="Enums.DataColumn">basic columns</see>
        /// </summary>
        /// <param name="data">The data.</param>
        /// <returns>List of cultures</returns>
        public List<string> ExtractCulturesFromResxCrunchDataSource(DataTable data)
        {
            List<string> cultures = new List<string>();

            // Determine cultures
            foreach (DataColumn dc in data.Columns)
            {
                if (!dc.ColumnName.Equals(Enums.DataColumn.Nr, StringComparison.OrdinalIgnoreCase) &&
                    !dc.ColumnName.Equals(Enums.DataColumn.Directory, StringComparison.OrdinalIgnoreCase) &&
                    !dc.ColumnName.Equals(Enums.DataColumn.File, StringComparison.OrdinalIgnoreCase) &&
                    !dc.ColumnName.Equals(Enums.DataColumn.Key, StringComparison.OrdinalIgnoreCase) &&
                    !dc.ColumnName.Equals(Enums.DataColumn.DefaultValue, StringComparison.OrdinalIgnoreCase))
                {
                    cultures.Add(dc.ColumnName.ToLowerInvariant());
                }
            }

            return cultures;
        }

        /// <summary>
        /// Filters the data from a datatable 
        /// Moves the filtered values to another table
        /// </summary>
        /// <param name="dataTable2Filter"></param>
        /// <param name="filteredValuesTable"></param>
        /// <param name="filters"></param>
        /// <param name="filterAfterColumnName"></param>
        /// <returns></returns>
        public static bool FilterData(ref DataTable dataTable2Filter, out DataTable filteredValuesTable, List<string> filters, string filterAfterColumnName)
        {
            if (dataTable2Filter == null || filters == null)
            {
                log.Error("At least one of the parameters was null");
                filteredValuesTable = null;
                return false;
            }

            try
            {
                filteredValuesTable = dataTable2Filter.Clone();

                int nrRows = dataTable2Filter.Rows.Count;
                for (int i = nrRows - 1; i >= 0; i--)
                {
                    // If the value needs to be filtered
                    if (filters.Contains(dataTable2Filter.Rows[i][filterAfterColumnName].ToString()))
                    {
                        // Move the row to the filtered values table
                        filteredValuesTable.ImportRow(dataTable2Filter.Rows[i]);
                        dataTable2Filter.Rows[i].Delete();
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                log.Error("Error while filtering", ex);
                filteredValuesTable = null;
                return false;
            }
        }

        /// <summary>
        /// Adds the values from a "patch" datatable
        /// The patch table is considered the base, so all the values overwrite the original data
        /// </summary>
        /// <param name="originalData">Datatable containing the data that needs to be updated</param>
        /// <param name="patchData">Data that contains the new info</param>
        /// <param name="keyColumns">Columns used as keys to identify/compare the data</param>
        /// <param name="notRelevantColumns">Columns ignored when comparing or copying data</param>
        /// <returns>True on success</returns>
        public static bool ApplyDataPatch(ref DataTable originalData, DataTable patchData, List<string> keyColumns, List<string> notRelevantColumns)
        {
            #region Check parameters
            if (patchData == null)
            {
                return false;
            }

            if (originalData == null)
            {
                originalData = patchData.Copy();

                return true;
            }
            #endregion Check parameters

            #region Verify structure of tables
            foreach (string keyColumn in keyColumns)
            {
                if (!originalData.Columns.Contains(keyColumn) || !patchData.Columns.Contains(keyColumn))
                {
                    log.Warn("The data is incosistent. Or the original or the patch data does not contain all the key columns");
                    return false;
                }
            }
            #endregion Verify structure of tables

            #region Columns containing values that need to be copied
            List<string> patchColumns = new List<string>();

            foreach (DataColumn col in patchData.Columns)
            {
                if (!keyColumns.Contains(col.ColumnName) && !notRelevantColumns.Contains(col.ColumnName))
                {
                    patchColumns.Add(col.ColumnName);
                }
            }
            #endregion Columns containing values that need to be copied

            #region Add missing columns to the original data
            foreach (string columnName in patchColumns)
            {
                if (!originalData.Columns.Contains(columnName))
                {
                    originalData.Columns.Add(columnName, patchData.Columns[columnName].DataType);
                }
            }
            #endregion Add missing columns to the original data

            #region Get list of columns containing values
            List<string> valueColumns = new List<string>();

            foreach (DataColumn col in originalData.Columns)
            {
                if (!keyColumns.Contains(col.ColumnName) && !notRelevantColumns.Contains(col.ColumnName) && patchData.Columns.Contains(col.ColumnName))
                {
                    valueColumns.Add(col.ColumnName);
                }
            }
            #endregion Get list of columns containing values

            #region Copy values
            foreach (DataRow dr in patchData.Rows)
            {
                foreach (DataRow originalRow in originalData.Rows)
                {
                    bool columnValuesMatch = true;

                    #region Check if this is the matching row
                    foreach (string key in keyColumns)
                    {
                        if (!dr[key].ToString().Equals(originalRow[key].ToString(), StringComparison.InvariantCultureIgnoreCase))
                        {
                            columnValuesMatch = false;

                            break;
                        }
                    }
                    #endregion Check if this is the matching row

                    // If this is the row
                    if (columnValuesMatch)
                    {
                        // Copy values
                        foreach (string valueKey in valueColumns)
                        {
                            originalRow[valueKey] = dr[valueKey].ToString();
                        }

                        break;
                    }
                }
            }
            #endregion Copy values

            return true;
        }
        #endregion Public Methods
    }
}
